package br.ufrgs.meetapp.models;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;

/***
 * Basic message to be extended to create more complex messages.
 * @author Benites
 *
 */
public abstract class MFTMessage {
	
	// Number of starting bytes in byte array when that defines a String. It is a byte order mark, that must be taken off when creating the serialized message to be sent to the server.		
	protected final int BYTE_ORDER_MARK_BYTES = 2;
		
	
	private final int MESSAGE_START_INDEX = 0;
	public static final int MESSAGE_CODE_INDEX = 1;
	private final int MESSAGE_BODY_LENGTH_INDEX = 2;
	protected final int MESSAGE_BODY_INDEX = 4;
	
	private final int LONG_SIZE_BYTES = 8;
	private final int DOUBLE_SIZE_BYTES = 8;
	private final int INT_SIZE_BYTES = 4;
	private final int SHORT_SIZE_BYTES = 2;
	private final int FLOAT_SIZE_BYTES = 2;
	
	private final byte MESSAGE_START = 0x02;
	private final byte MESSAGE_END = 0x03;
	
	protected byte[] byteMessage;
	private int messageMaxSize;
	
	/***
	 * Creates an internal byte array corresponding to the message with the given body size and a start tag
	 * @param byteMessageBodySize
	 */
	protected void createByteMessage(int byteMessageBodySize, byte messageCode){
		messageMaxSize = byteMessageBodySize + 5; // adds 5 bytes corresponding to the message start(1), message type(1), body length(2) and message end(1) 
		byteMessage = new byte[messageMaxSize];
		
		writeByteAtMessageIndex(MESSAGE_START, MESSAGE_START_INDEX);
		
		writeByteAtMessageIndex(messageCode, MESSAGE_CODE_INDEX);
		
		writeShortAtMessageIndex(byteMessageBodySize, MESSAGE_BODY_LENGTH_INDEX);
	}
	
	/***
	 * Finishes the previously created internal byte array with a end tag  
	 * @return Returns true if the message was successfully finished
	 */
	protected void finishByteMessage(){
		try {
			byteMessage[messageMaxSize - 1] = MESSAGE_END;
		} catch (Exception e) {
			System.out.println("Error in ByteMessage.finishByteMessage -  finishing message with end tag");
			if (e.getMessage() != null){
				System.out.println(e.getMessage());
			}
			e.printStackTrace();
		}
	}
	
	/***
	 * Writes a byte in the specified index of the internal array
	 * @param byteValue
	 * @param index
	 */
	protected void writeByteAtMessageIndex(byte byteValue, int index){
		try {
			byteMessage[index] = byteValue;
		} catch (Exception exception) {

			System.out.println("Error in ByteMessage.writeByteAtMessageIndex - trying to write byte in message byte array ");
			
			if (exception.getMessage() != null)
				System.out.println(exception.getMessage());
			exception.printStackTrace();
		}
	}
	
	/***
	 * Writes a long in the specified index of the internal array. <br />
	 * The value is inverted before writing cause the server that will receive the byte message is running above a little-endian architecture and <br />
	 * Java transforms a type into a byte array using the big-endian order
	 * @param longValue
	 * @param index
	 */
	protected void writeLongAtMessageIndex(long longValue, int index){
		
		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
		byte[] byteMessageTemp;
		
		try {
			dataOutputStream.writeLong(longValue);
			dataOutputStream.close();
			
			byteMessageTemp = byteOutputStream.toByteArray();
			
			// Invert byte array, cause the server receives it as little-endian
			invertByteArray(byteMessageTemp);
					
			System.arraycopy(byteMessageTemp, 0, byteMessage, index, LONG_SIZE_BYTES);
			
		} catch (Exception e) {
			System.out.println("Error in Message.writeLongAtMessageIndex - trying to write longValue in dataOutputStream");
			if (e.getMessage() != null){
				System.out.println(e.getMessage());
			}	
			e.printStackTrace();
		}
	}
	
	/***
	 * Converts a String to short and writes the specified index of the internal array
	 * The value is inverted before writing cause the server that will receive the byte message is running above a little-endian architecture and <br />
	 * Java transforms a type into a byte array using the big-endian order
	 * @param stringValue 
	 * @param index
	 */
	protected void writeShortAtMessageIndex(String stringValue, int index){

		if(stringValue != null && stringValue.length() > 0) {
		
			ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
			DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
			byte[] byteMessageTemp;
			
			try {
				
				dataOutputStream.writeShort(Short.parseShort(stringValue));
				dataOutputStream.close();
				
				byteMessageTemp = byteOutputStream.toByteArray();
				
				// Invert byte array, cause the server receives it as little-endian
				invertByteArray(byteMessageTemp);
				System.arraycopy(byteMessageTemp, 0, byteMessage, index, SHORT_SIZE_BYTES);
				
			} catch (Exception e) {
				System.out.println("Error in Message.writeShortAtMessageIndex - trying to parse stringValue to short and write in dataOutputStream");
				if (e.getMessage() != null){
					System.out.println(e.getMessage());
				}	
				e.printStackTrace();
			}
		}
	}
	
	/***
	 * Writes a string in the specified index of the internal array. <br />
	 * @param stringValue
	 * @param index
	 */
	protected void writeStringAtMessageIndex(String stringValue, int index) {
		try {
			byte[] byteMessageTemp = getStringByteArray(stringValue);
			System.arraycopy(byteMessageTemp, BYTE_ORDER_MARK_BYTES, byteMessage, index, (byteMessageTemp.length - BYTE_ORDER_MARK_BYTES));
		} catch (Exception exception) {

			System.out
					.println("Error in IdentificationMessage.writeIdentificationBody - trying to ");
			if (exception.getMessage() != null)
				System.out.println(exception.getMessage());
			exception.printStackTrace();
		}
	}
	
	
	/***
	 * Get the corresponding byte array from a specific string value
	 * @param stringValue 
	 */
	protected byte[] getStringByteArray(String stringValue){
		
		byte[] stringByteArray = null;
		
		if (stringValue != null && stringValue != ""){
			ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
			DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
			
			try {
				dataOutputStream.writeUTF(stringValue);
				dataOutputStream.close();
				stringByteArray = byteOutputStream.toByteArray();
			} catch (Exception exception) {

				System.out
						.println("Error in MFTMessage.getStringByteArray - trying to convert string into byte array ");
				if (exception.getMessage() != null)
					System.out.println(exception.getMessage());
				exception.printStackTrace();
			}
		}
		
		return stringByteArray;
	}
	
	/***
	 * Writes a short in the specified index of the internal array
	 * The value is inverted before writing cause the server that will receive the byte message is running above a little-endian architecture and <br />
	 * Java transforms a type into a byte array using the big-endian order
	 * @param shortValue
	 * @param index
	 */
	protected void writeShortAtMessageIndex(short shortValue, int index){
		
		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
		byte[] byteMessageTemp;
		
		try {
			dataOutputStream.writeShort(shortValue);
			dataOutputStream.close();
			
			byteMessageTemp = byteOutputStream.toByteArray();
			
			// Invert byte array, cause the server receives it as little-endian
			invertByteArray(byteMessageTemp);
					
			System.arraycopy(byteMessageTemp, 0, byteMessage, index,
					SHORT_SIZE_BYTES);
			
		} catch (Exception e) {
			System.out.println("Error in Message.writeShortAtMessageIndex - trying to write short in dataOutputStream");
			if (e.getMessage() != null){
				System.out.println(e.getMessage());
			}	
			e.printStackTrace();
		}

		
	}
	
	/***
	 * Converts a Float to short and Writes in the specified index of the internal array
	 * The value is inverted before writing cause the server that will receive the byte message is running above a little-endian architecture and <br />
	 * Java transforms a type into a byte array using the big-endian order
	 * @param floatValue
	 * @param index
	 */
	protected void writeShortAtMessageIndex(float floatValue, int index){
		
		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
		byte[] byteMessageTemp;
		
		try {
			dataOutputStream.writeShort((short)floatValue);
			dataOutputStream.close();
			
			byteMessageTemp = byteOutputStream.toByteArray();
			
			// Invert byte array, cause the server receives it as little-endian
			invertByteArray(byteMessageTemp);
			
			System.arraycopy(byteMessageTemp, 0, byteMessage, index,
					SHORT_SIZE_BYTES);
			
		} catch (Exception e) {
			System.out.println("Error in Message.writeShortAtMessageIndex - trying to write short in dataOutputStream");
			if (e.getMessage() != null){
				System.out.println(e.getMessage());
			}	
			e.printStackTrace();
		}
	}
	
	/***
	 * Converts an int to short and writes in the specified index of the internal array
	 * The value is inverted before writing cause the server that will receive the byte message is running above a little-endian architecture and <br />
	 * Java transforms a type into a byte array using the big-endian order
	 * @param intValue
	 * @param index
	 */
	protected void writeShortAtMessageIndex(int intValue, int index){
		
		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
		byte[] byteMessageTemp;
		
		try {
			dataOutputStream.writeShort((short)intValue);
			dataOutputStream.close();
			
			byteMessageTemp = byteOutputStream.toByteArray();
			
			// Invert byte array, cause the server receives it as little-endian
			invertByteArray(byteMessageTemp);
			
			System.arraycopy(byteMessageTemp, 0, byteMessage, index,
					SHORT_SIZE_BYTES);
			
		} catch (Exception e) {
			System.out.println("Error in Message.writeShortAtMessageIndex - trying to write short in dataOutputStream");
			if (e.getMessage() != null){
				System.out.println(e.getMessage());
			}	
			e.printStackTrace();
		}
	}
	
	/***
	 * Writes an int in the specified index of the internal array
	 * The value is inverted before writing cause the server that will receive the byte message is running above a little-endian architecture and <br />
	 * Java transforms a type into a byte array using the big-endian order
	 * @param intValue
	 * @param index
	 */
	protected void writeIntAtMessageIndex(int intValue, int index){
		
		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
		byte[] byteMessageTemp;
		
		try {
			dataOutputStream.writeInt(intValue);
			dataOutputStream.close();
			
			byteMessageTemp = byteOutputStream.toByteArray();
			
			// Invert byte array, cause the server receives it as little-endian
			invertByteArray(byteMessageTemp);
					
			System.arraycopy(byteMessageTemp, 0, byteMessage, index,
					INT_SIZE_BYTES);
			
		} catch (Exception e) {
			System.out.println("Error in Message.writeIntAtMessageIndex - trying to write intValue in dataOutputStream");
			if (e.getMessage() != null){
				System.out.println(e.getMessage());
			}	
			e.printStackTrace();
		}
	}
	
	/***
	 * Converts a Double to int and writes in the specified index of the internal array
	 * The value is inverted before writing cause the server that will receive the byte message is running above a little-endian architecture and <br />
	 * Java transforms a type into a byte array using the big-endian order
	 * @param doubleValue
	 * @param index
	 */
	protected void writeIntAtMessageIndex(double doubleValue, int index){
		
		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
		byte[] byteMessageTemp;
		
		try {
			dataOutputStream.writeInt((int)doubleValue);
			dataOutputStream.close();
			
			byteMessageTemp = byteOutputStream.toByteArray();
			
			// Invert byte array, cause the server receives it as little-endian
			invertByteArray(byteMessageTemp);
					
			System.arraycopy(byteMessageTemp, 0, byteMessage, index,
					INT_SIZE_BYTES);
			
		} catch (Exception e) {
			System.out.println("Error in Message.writeIntAtMessageIndex - trying to write intValue in dataOutputStream");
			if (e.getMessage() != null){
				System.out.println(e.getMessage());
			}	
			e.printStackTrace();
		}
	}
	
	/***
	 * Writes a Float in the specified index of the internal array	 
	 * The value is inverted before writing cause the server that will receive the byte message is running above a little-endian architecture and <br />
	 * Java transforms a type into a byte array using the big-endian order
	 * @param floatValue
	 * @param index
	 */
	protected void writeFloatAtMessageIndex(float floatValue, int index){
		
		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
		byte[] byteMessageTemp;
		
		try {
			dataOutputStream.writeFloat(floatValue);
			dataOutputStream.close();
			
			byteMessageTemp = byteOutputStream.toByteArray();
			
			// Invert byte array, cause the server receives it as little-endian
			invertByteArray(byteMessageTemp);
			
			System.arraycopy(byteMessageTemp, 0, byteMessage, index,FLOAT_SIZE_BYTES);
			
		} catch (Exception e) {
			System.out.println("Error in Message.writeIntAtMessageIndex - trying to write intValue in dataOutputStream");
			if (e.getMessage() != null){
				System.out.println(e.getMessage());
			}	
			e.printStackTrace();
		}
	}
	
	/***
	 * Converts a Double to Float and writes in the specified index of the internal array	 
	 * The value is inverted before writing cause the server that will receive the byte message is running above a little-endian architecture and <br />
	 * Java transforms a type into a byte array using the big-endian order
	 * @param floatValue
	 * @param index
	 */
	protected void writeFloatAtMessageIndex(double doubleValue, int index){
		
		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
		byte[] byteMessageTemp;
		
		try {
			dataOutputStream.writeFloat((float)doubleValue);
			dataOutputStream.close();
		
			byteMessageTemp = byteOutputStream.toByteArray();
			
			// Invert byte array, cause the server receives it as little-endian
			invertByteArray(byteMessageTemp);
			
			System.arraycopy(byteMessageTemp, 0, byteMessage, index,FLOAT_SIZE_BYTES);
			
		} catch (Exception e) {
			System.out.println("Error in Message.writeIntAtMessageIndex - trying to write intValue in dataOutputStream");
			if (e.getMessage() != null){
				System.out.println(e.getMessage());
			}	
			e.printStackTrace();
		}
	}
	
	/***
	 * Writes a Double in the specified index of the internal array	 
	 * The value is inverted before writing cause the server that will receive the byte message is running above a little-endian architecture and <br />
	 * Java transforms a type into a byte array using the big-endian order
	 * @param floatValue
	 * @param index
	 */
	protected void writeDoubleAtMessageIndex(double doubleValue, int index){
		
		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
		DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream);
		byte[] byteMessageTemp;
		
		try {
			dataOutputStream.writeDouble(doubleValue);
			dataOutputStream.close();
			
			byteMessageTemp = byteOutputStream.toByteArray();
			
			// Invert byte array, cause the server receives it as little-endian
			invertByteArray(byteMessageTemp);
			
			System.arraycopy(byteMessageTemp, 0, byteMessage, index,DOUBLE_SIZE_BYTES);
			
		} catch (Exception e) {
			System.out.println("Error in Message.writeIntAtMessageIndex - trying to write intValue in dataOutputStream");
			if (e.getMessage() != null){
				System.out.println(e.getMessage());
			}	
			e.printStackTrace();
		}
	}
	
	public abstract byte[] getSerializedMessage();
	
	/***
	 * Invert a received byte array
	 * @param byteArray - array to be inverted
	 */
	protected void invertByteArray(byte[] byteArray) {
		
		byte[] byteArrayTemp = new byte[byteArray.length];
		byteArrayTemp = byteArray.clone();
		
		
		for (int i = 0; i < byteArray.length; i++) {
			byteArray[i] = byteArrayTemp[byteArray.length - i - 1];
		}
	}
}
